from functools import reduce
from functools import wraps
from functools import partial
import time
# Part I:高阶函数: Higher-order function
# 函数名本身是指向变量的指针，其作为另一函数的接收函数时，该函数被称为高阶函数
# 函数式编程: 将函数作为参数传入，是一种高度抽象的编程范式

def add_func1(x, y, f):
    return f(x) + f(y)

print(add_func1(5, -4, abs))

# map/reduce
list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))  # map返回的Iterator是惰性序列，list函数将其整体计算出来

# reduce的实际效果: reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
def add_func2(x, y):
    return x + y
print(reduce(add_func2, [1, 3, 5, 7, 9]))

# 利用map/reduce自定义一个int()函数
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def char2num(s):
    return DIGITS[s]

def str2int(s):
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))

print(str2int("123"))

# 练习1: 字符串标准化
def normalize(name):
     s = [v.upper() if i==0 else v.lower() for i,v in enumerate(name)]
     return "".join(s)

print(normalize("amY"))

# 练习2: 列表整数相乘
def prob(L):
    def multi_func(x, y):
        return x * y

    return reduce(multi_func, L)

print(prob([3, 5, 7, 9]))

# 练习3: 字符串转浮点数
def str2float(s):
    integer_part = s[0: s.index('.')]
    decimal_part = s[s.index('.') + 1:]

    return reduce(lambda x, y: x * 10 + y, map(lambda x: DIGITS[x], integer_part)) + reduce(lambda x, y: x / 10 + y, map(lambda x: DIGITS[x], decimal_part[: : -1] + '0'))

print(str2float("123.456"))

# filter 过滤器
def is_odd(n):
    return n % 2 == 1

print(list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])))    # 奇数过滤器

def odd_sequence():      # 奇数生成器
    n = 1
    while True:
        n = n + 2
        yield n

def not_divisible(n):
    return lambda x: x % n > 0

def prime_sequence():    # 无限循环素数序列
    yield 2
    it = odd_sequence()  # 初始化奇数序列
    while True:
        n = next(it)
        yield n
        it = filter(not_divisible(n), it)

for n in prime_sequence():
    if n < 100:
        print(n)
    else:
        break

# 练习：回文数判别
def is_palindrome(n):
    return str(n) == str(n)[: : -1]

print(list(filter(is_palindrome, range(1, 200))))

# sort 函数
print(sorted([36, 5, -12, 9, -21], key = abs))
print(sorted(['bob', 'about', 'Zoo', 'Credit'], key = str.lower, reverse = True))

# Part Ⅱ: 返回函数（闭包）
# 返回闭包时牢记一点：返回函数不要引用任何循环变量，或者后续会发生变化的变量！
def createCounter():
    i = 0
    def counter():
        nonlocal i
        i = i + 1
        return i
    return counter

counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA())

# Part Ⅲ: 匿名函数（仅有一个表达式，返回值即为该表达式的结果）
f = lambda x: x * x
print(f(5))
print(list(filter(lambda n: n % 2 == 1, range(1, 20))))       # Python对匿名函数的支持有限，仅一些简单的情况可以用匿名函数

# Part Ⅳ: 装饰器（在代码运行期间动态增加功能，本质上，decorator就是一个返回函数的高阶函数）
def log(func):
    def wrapper(*args, **kw):
        print('log: call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

@log                       # 此处等价于 now = log(now)
def now1():
    print("2020-11-3")

now1()

def log_with_info(text):                      # 第一层：decorator本身需要传入的参数
    def decorator(func):                      # 第二层：返回函数
        @wraps(func)
        def wrapper(*args, **kw):             # 第三层：被调用函数传入的参数
            print("%s %s():" % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

@log_with_info('execute')
def now2():
    print("2020-11-4")

now2()
print(now2.__name__)

# 练习: 打印函数的执行时间
def log_with_runtime(func):
    def wrapper(*args, **kw):
        begintime = time.time()
        ret = func(*args, **kw)
        endtime = time.time()
        print('%s executed in %s ms' % (func.__name__, endtime - begintime))
        return ret
    return wrapper

@log_with_runtime
def test_runtime():
    time.sleep(0.02)
    return

test_runtime()

# Part Ⅲ: 偏函数（将函数的某些参数设置为默认值，返回一个新的函数）
int2 = partial(int, base = 2)
print(int2('100101'))
print(int2('10110100', base = 8))    # 调用时也可以指定其它值

print()